﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
using System.Reflection;
using System.Collections;
using Unity;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;

namespace Tnelab.MBWrapper
{
    public static class JsonElementExtension
    {
        class CallJsResultInfo { 
            public bool Status { get; set; }
            public object Result { get; set; }
            public string ErrorMessage { get; set; }
        }
        class CallJsArgsInfo
        {
            public object[] Args { get; set; }
        }
        static object GetValueForIList(JsonElement srcData, Type type,IServiceWebView webView)
        {
            object result = null;
            if (type.FullName.StartsWith("System.Collections.Generic.IList"))
            {
                var subType = type.GetGenericArguments()[0];
                var gtype = Type.GetType("System.Collections.Generic.List`1").MakeGenericType(subType);
                IList robj = (IList)Activator.CreateInstance(gtype);
                var tobj = srcData.EnumerateArray().Select(it =>
                {
                    return GetValue(it, subType,webView);
                }).ToArray();
                foreach (var elm in tobj)
                {
                    robj.Add(elm);
                }
                result = robj;
            }
            else if (type.GetInterface("System.Collections.IList") != null)
            {
                var subType = type.GetGenericArguments()[0];
                IList robj = (IList)Activator.CreateInstance(type);
                var tobj = srcData.EnumerateArray().Select(it =>
                {
                    return GetValue(it, subType,webView);
                }).ToArray();
                foreach (var elm in tobj)
                {
                    robj.Add(elm);
                }
                result = robj;
            }
            return result;
        }
        static object GetValueForArray(JsonElement srcData, Type type, IServiceWebView webView)
        {
            object result = null;
            if (type.BaseType == typeof(Array))
            {
                var getMethodInfo = type.GetMethod("Get");
                var subType = getMethodInfo.ReturnType;
                Array robj = (Array)Activator.CreateInstance(type, srcData.GetArrayLength());

                srcData.EnumerateArray().Select(it =>
                {
                    return GetValue(it, subType,webView);
                }).ToArray().CopyTo(robj, 0);
                result = robj;
            }
            else
            {
                result = GetValueForIList(srcData, type,webView);
            }
            return result;
        }
        static object FuncCallJs(params object[] objs)
        {
            var webView = objs[0] as IServiceWebView;
            var script = objs[1].ToString();
            var returnType = objs[2] as Type;
            var args = new object[objs.Length - 3];
            for (var i = 0; i < args.Length; i++)
            {
                args[i] = objs[i + 3];
            }
            var callJsArgsInfo = new CallJsArgsInfo();
            callJsArgsInfo.Args = args;
            var json = JsonSerializer.Serialize(args);
            script = $"return ({script})(\"{TneEncoder.Escape(json)}\")";
            var result = webView.RunJsAsync(script).GetAwaiter().GetResult();
            result = TneEncoder.UnEscape(result);
            var robj = JsonSerializer.Deserialize<CallJsResultInfo>(result);
            if (!robj.Status)
                throw new Exception(robj.ErrorMessage);
            return ((JsonElement)robj.Result).GetValue(returnType, webView);
        }
        static void ActionCallJs(params object[] objs)
        {
            var webView = objs[0] as IServiceWebView;
            var script = objs[1].ToString();
            var returnType = objs[2] as Type;
            var args = new object[objs.Length - 3];
            for (var i = 0; i < args.Length; i++)
            {
                args[i] = objs[i + 3];
            }
            var callJsArgsInfo = new CallJsArgsInfo();
            callJsArgsInfo.Args = args;
            var json = JsonSerializer.Serialize(args);
            script = $"return ({script})(\"{TneEncoder.Escape(json)}\")";
            var result = webView.RunJsAsync(script).GetAwaiter().GetResult();
            result = TneEncoder.UnEscape(result);
            var robj = JsonSerializer.Deserialize<CallJsResultInfo>(result);
            if (!robj.Status)
                throw new Exception(robj.ErrorMessage);
        }
        static object GetValueForDelegate(JsonElement srcData,Type type, IServiceWebView webView)
        {
            object result = null;

            var script = srcData.GetString();

            var delegateInfo = type.GetMethod("Invoke");
            bool theIsAction = delegateInfo.ReturnType == typeof(void);
            var paramTypes = delegateInfo.GetParameters().Select(it => it.ParameterType).ToArray();

            List<ParameterExpression> paramExps = new List<ParameterExpression>();
            List<Expression> vParamExps = new List<Expression>() {
                //Expression.Convert(Expression.Constant(theBrowser), typeof(object)),
                Expression.Convert(Expression.Constant(webView), typeof(object)),
                Expression.Convert(Expression.Constant(script), typeof(object)),
                Expression.Convert(Expression.Constant(delegateInfo.ReturnType),typeof(object))
            };
            var len = paramTypes.Length;
            for (var i = 0; i < len; i++)
            {
                var pt = paramTypes[i];
                var pEx = Expression.Parameter(pt);
                paramExps.Add(pEx);
                vParamExps.Add(Expression.Convert(pEx, typeof(object)));
            }
            var arrayExp = Expression.NewArrayInit(typeof(object), vParamExps);
            LambdaExpression lambdaExpr;
            if (theIsAction)
            {
                var method = typeof(JsonElementExtension).GetMethod("ActionCallJs", BindingFlags.NonPublic | BindingFlags.Static);
                //lambdaExpr = Expression.Lambda(theNativeType, Expression.Call(Expression.Constant(this), method, arrayExp), paramExps);
                lambdaExpr = Expression.Lambda(type, Expression.Call(method, arrayExp), paramExps);
            }
            else
            {
                var method = typeof(JsonElementExtension).GetMethod("FuncCallJs", BindingFlags.NonPublic | BindingFlags.Static);
                lambdaExpr = Expression.Lambda(type, Expression.Convert(Expression.Call(method, arrayExp), delegateInfo.ReturnType), paramExps);
            }
            result= lambdaExpr.Compile();
            return result;
        }
        static object GetValueForString(JsonElement srcData,Type type,IServiceWebView webView)
        {
            object result = null;
            if (type == typeof(string))
            {
                result = srcData.GetString();
            }
            else if (type.FullName.StartsWith("System.Action"))
            {                                
                result = GetValueForDelegate(srcData, type, webView);
            }
            else if (type.FullName.StartsWith("System.Func"))
            {
                result = GetValueForDelegate(srcData, type, webView);
            }
            return result;
        }
        public static object GetValue(this JsonElement srcData, Type type,IServiceWebView webView)
        {
            object result = null;

            switch (srcData.ValueKind)
            {
                case JsonValueKind.Null:
                    result = null;
                    break;
                case JsonValueKind.Number:
                    result = srcData.GetDouble();
                    result = Convert.ChangeType(result, type);
                    break;
                case JsonValueKind.False:
                    result = false;
                    break;
                case JsonValueKind.True:
                    result = true;
                    break;
                case JsonValueKind.Undefined:
                    result = null;
                    break;
                case JsonValueKind.String:
                    result = GetValueForString(srcData, type, webView);
                    break;
                case JsonValueKind.Object:
                    var ps = type.GetProperties();
                    result = Activator.CreateInstance(type);
                    foreach (var p in ps)
                    {
                        var val = GetValue(srcData.GetProperty(p.Name), p.PropertyType, webView);
                        p.SetValue(result, val);
                    }
                    break;
                case JsonValueKind.Array:
                    result = GetValueForArray(srcData, type,webView);
                    break;
            }
            return result;
        }
    }
    class ServiceInvokeInfo{
        public string ServicePath { get; set; }
        public string MethodName { get; set; }
        public object[] Args { get; set; }
    }
    class ServiceInvokeResultInfo
    {
        public bool Status { get; set; }
        public string Request { get; set; }
        public string ErrorMessage { get; set; }
        public object Result { get; set; }
    }
    class ServiceWebView : IServiceWebView
    {        
        protected IWebView WebView { get; set; }
        protected IUnityContainer ServiceContainer { get; set; }
        public ServiceWebView(IWebView webView, IUnityContainer serviceContainer)
        {
            WebView = webView;
            ServiceContainer = serviceContainer;
            this.WebView.LoadUrlBegin += WebView_LoadUrlBegin;
            this.WebView.LoadUrlEnd += WebView_LoadUrlEnd;
            this.WebView.PaintBitUpdated += WebView_PaintBitUpdated;
            this.WebView.JsCallNative += WebView_JsCallNative;
        }

        private void WebView_LoadUrlEnd(object sender, LoadUrlEndArgs e)
        {
            if (this.LoadUrlEnd != null)
            {
                this.LoadUrlEnd(sender, e);
            }
        }

        private void WebView_LoadUrlBegin(object sender, LoadUrlBeginArgs e)
        {
            if (this.LoadUrlBegin != null)
            {
                this.LoadUrlBegin(sender, e);
            }
        }

        private void WebView_JsCallNative(object sender, JsCallNativeEventArg e)
        {
            var request = TneEncoder.UnEscape(e.Request);
            var serviceInvokeResultInfo = new ServiceInvokeResultInfo();
            try
            {
                JsonSerializerOptions options = new JsonSerializerOptions()
                {
                    DefaultBufferSize = 1024,
                    MaxDepth = 2
                };
                var serviceInvokeInfo = JsonSerializer.Deserialize<ServiceInvokeInfo>(request, options);
                var type = Type.GetType(serviceInvokeInfo.ServicePath);
                var service = ServiceContainer.Resolve(type);
                var methodInfo = service.GetType().GetMethod(serviceInvokeInfo.MethodName);
                var paramList = methodInfo.GetParameters();
                var t = paramList[1].ParameterType.FullName;
                var args = new object[paramList.Length];
                for (var i = 0; i < args.Length; i++)
                {
                    args[i] = ((JsonElement)serviceInvokeInfo.Args[i]).GetValue(paramList[i].ParameterType, this as IServiceWebView);
                }
                serviceInvokeResultInfo.Result = methodInfo.Invoke(service, args);
                serviceInvokeResultInfo.Status = true;
            }
            catch (Exception ex)
            {
                serviceInvokeResultInfo.Status = false;
                serviceInvokeResultInfo.Request = request;
                serviceInvokeResultInfo.ErrorMessage = ex.ToString();
            }
            var response = JsonSerializer.Serialize<ServiceInvokeResultInfo>(serviceInvokeResultInfo);
            this.WebView.ResponseJsCallNative(e.CallId, e.MessageId, TneEncoder.Escape(response));
        }
        private void WebView_PaintBitUpdated(object sender, PaintBitUpdatedEventArg e)
        {
            if (PaintBitUpdated != null)
            {
                this.PaintBitUpdated(this, e);
            }
        }
        public bool Transparent { get => WebView.Transparent; set => WebView.Transparent=value; }

        public bool IsVip => WebView.IsVip;

        public Size Size => WebView.Size;

        public bool LoadUrlEndEnable { get => this.WebView.LoadUrlEndEnable; set => this.WebView.LoadUrlEndEnable = value; }

        public event EventHandler<LoadUrlBeginArgs> LoadUrlBegin;
        public event EventHandler<LoadUrlEndArgs> LoadUrlEnd;

        public event EventHandler<PaintBitUpdatedEventArg> PaintBitUpdated;

        public bool FireKeyDownEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return WebView.FireKeyDownEvent(virtualKeyCode, flags, systemKey);
        }

        public bool FireKeyPressEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return WebView.FireKeyPressEvent(virtualKeyCode, flags, systemKey);
        }

        public bool FireKeyUpEvent(uint virtualKeyCode, uint flags, bool systemKey)
        {
            return WebView.FireKeyUpEvent(virtualKeyCode, flags, systemKey);
        }

        public bool FireMouseEvent(uint message, int x, int y, uint flags)
        {
            return WebView.FireMouseEvent(message, x, y, flags);
        }

        public bool FireMouseWheelEvent(int x, int y, int delta, uint flags)
        {
            return WebView.FireMouseWheelEvent(x, y, delta, flags);
        }

        public CursorType GetCursorType()
        {
            return WebView.GetCursorType();
        }

        public void KillFocus()
        {
            WebView.KillFocus();
        }

        public void LoadUrl(Uri url)
        {
            WebView.LoadUrl(url);
        }

        public void Resize(int width, int height)
        {
            WebView.Resize(width, height);
        }

        public void SetFocus()
        {
            WebView.SetFocus();
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        bool is_disposed = false;
        protected void Dispose(bool disposing)
        {
            if (!is_disposed)
            {
                if (disposing)
                {
                    //Clean Up managed resources  
                }
                this.WebView.Dispose();
            }
            is_disposed = true;
        }
        public Action<Action> UIInvoke { get=>this.WebView.UIInvoke; }
        Task<string> IServiceWebView.RunJsAsync(string script)
        {
            return this.WebView.RunJsAsync(script);
        }

        ~ServiceWebView()
        {
            Dispose(false);
        }
    }
}
